from __future__ import annotations
import json
from enum import Enum
from numbers import Number
from typing import Union, Dict, List, TypeVar, Literal

# ----------------  定义类型和枚举   ---------------------

PlainObject = Dict[str, object]
MixedDict = Union[str, Dict[str, object], None, bool]
NumberAndStr = Union[str, Number]
SchemaTpl = str

T = TypeVar("T")
K = TypeVar("K")
V = TypeVar("V")

SchemaExpression = str
SchemaName = str
SchemaIcon = str
SchemaClassName = Union[str, Dict[str, object]]
PresetColor = Union[str, Dict[str, object]]
SchemaType = Literal["", "form", "alert", "app", "audio", "avatar", "button-group", "breadcrumb",
                     "card", "card2", "cards", "carousel", "chart", "calendar", "collapse", "collapse-group",
                     "color", "crud", "crud2", "custom", "date", "static-date", "datetime", "static-datetime",
                     "time", "static-time", "month", "static-month", "date-range", "dialog", "spinner",
                     'spinner', 'divider', 'dropdown-button', 'drawer', 'each', 'flex', 'flex-item', 'grid-2d', 'icon',
                     'iframe', 'image', 'static-image', 'images', 'static-images', 'json-schema', 'json-schema-editor',
                     'json', 'static-json', 'link', 'list', 'log', 'static-list', 'map', 'mapping', 'markdown', 'nav',
                     'number', 'page', 'pagination', 'pagination-wrapper', 'property', 'operation', 'plain', 'text',
                     'progress', 'qrcode', 'qr-code', 'barcode', 'remark', 'search-box', 'sparkline', 'status', 'table',
                     'static-table', 'table2', 'html', 'tpl', 'tasks', 'vbox', 'video', 'wizard', 'wrapper',
                     'web-component', 'anchor-nav', 'steps', 'timeline', 'control', 'input-array', 'button', 'submit',
                     'reset', 'button-group-select', 'button-toolbar', 'chained-select', 'chart-radios', 'checkbox',
                     'checkboxes', 'input-city', 'input-color', 'combo', 'condition-builder', 'container',
                     'switch-container', 'input-date', 'input-datetime', 'input-time', 'input-quarter', 'input-year',
                     'input-month', 'input-date-range', 'input-time-range', 'input-datetime-range', 'input-excel',
                     'input-formula', 'diff-editor', 'office-viewer', 'editor', 'bat-editor', 'c-editor',
                     'coffeescript-editor', 'cpp-editor', 'csharp-editor', 'css-editor', 'dockerfile-editor',
                     'fsharp-editor', 'go-editor', 'handlebars-editor', 'html-editor', 'ini-editor', 'java-editor',
                     'javascript-editor', 'json-editor', 'less-editor', 'lua-editor', 'markdown-editor', 'msdax-editor',
                     'objective-c-editor', 'php-editor', 'plaintext-editor', 'postiats-editor', 'powershell-editor',
                     'pug-editor', 'python-editor', 'r-editor', 'razor-editor', 'ruby-editor', 'sb-editor',
                     'scss-editor', 'sol-editor', 'sql-editor', 'swift-editor', 'typescript-editor', 'vb-editor',
                     'xml-editor', 'yaml-editor', 'fieldset', 'fieldSet', 'input-file', 'formula', 'grid', 'group',
                     'hbox', 'hidden', 'icon-picker', 'icon-select', 'input-image', 'input-group', 'list-select',
                     'location-picker', 'matrix-checkboxes', 'input-month-range', 'input-quarter-range', 'nested-select',
                     'input-number', 'panel', 'picker', 'radio', 'radios', 'input-range', 'input-rating', 'input-repeat',
                     'input-rich-text', 'select', 'service', 'static', 'input-sub-form', 'switch', 'input-table', 'tabs',
                     'tabs-transfer', 'input-tag', 'input-text', 'input-password', 'input-email', 'input-url', 'uuid',
                     'multi-select', 'textarea', 'transfer', 'transfer-picker', 'tabs-transfer-picker', 'input-tree',
                     'tree-select', 'table-view', 'portlet', 'grid-nav', 'users-select', 'tag', 'tags', 'words',
                     'password', 'multiline-text', 'amis', 'native-date', 'native-time', 'native-number', 'code',
                     'tooltip-wrapper'
]
SchemaIcon = str
SchemaUrlPath = str


# Direction = Literal["horizontal", "vertical"]
# LineStyle = Literal["dashed", "solid"]
# Position = Literal["left", "center", "right"]
# SelectMode = Literal["group", "table", "tree", "chained", "associated"]


class Direction(Enum):
    none = ""
    horizontal = "horizontal"
    vertical = "vertical"


class LineStyle(Enum):
    none = ""
    dashed = "dashed"
    solid = "solid"


class Position(Enum):
    none = ""
    left = "left"
    center = "center"
    right = "right"
    top_right = "top-right"
    top_left = "top-left"
    top_center = "top-center"
    bottom_left = "bottom-left"
    bottom_right = "bottom-right"
    bottom_center = "bottom-center"


class LeftAndRight(Enum):
    none = ""
    left = "left"
    right = "right"


class Placement(Enum):
    none = ""
    top = "top"
    right = "right"
    bottom = "bottom"
    left = "left"


class Trigger(Enum):
    none = ""
    hover = "hover"
    focus = "focus"


class OrderDir(Enum):
    none = ""
    asc = "asc"
    desc = "desc"


class Size(Enum):
    none = ""
    xs = "xs"
    sm = "sm"
    md = "md"
    lg = "lg"
    xl = "xl"
    full = "full"


# class DialogSize(Enum):
#     none = ""
#     xs = "xs"
#     sm = "sm"
#     md = "md"
#     lg = "lg"
#     xl = "xl"
#     full = "full"


class SelectMode(Enum):
    none = ""
    group = "hover"
    table = "table"
    tree = "tree"
    chained = "chained"
    associated = "associated"


class Method(Enum):
    none = ""
    get = "get"
    post = "post"
    put = "put"
    delete = "delete"
    patch = "patch"
    jsonp = "jsonp"
    js = "js"


class LabelAlign(Enum):
    none = ""
    right = "right"
    left = "left"


class ActionType(Enum):
    none = ""
    button = "button"
    reset = "reset"
    submit = "submit"
    clear = "clear"
    url = "url"
    dialog = "dialog"
    ajax = "ajax"
    link = "link"
    confirm = "confirm"
    drawer = "drawer"
    cancel = "cancel"
    prev = "prev"
    next = "next"
    copy = "copy"
    close = "close"
    download = "download"
    email = "email"
    saveAs = "saveAs"
    toast = "toast"  # 类型为toast，则需要配置toast属性


class Level(Enum):
    none = ""
    default = "default"
    link = "link"
    primary = "primary"
    enhance = "enhance"
    secondary = "secondary"
    info = "info"
    success = "success"
    warning = "warning"
    danger = "danger"
    light = "light"
    dark = "dark"


class DateShortcuts(Enum):
    none = ""
    today = "today"  # 今天
    yesterday = "yesterday"  # 昨天
    tomorrow = "tomorrow"  # 明天
    prevweek = "prevweek"  # 上周
    thisweek = "thisweek"  # 这个周
    thismonth = "thismonth"  # 本月
    prevmonth = "prevmonth"  # 上个月
    prevquarter = "prevquarter"  # 上个季度
    thisquarter = "thisquarter"  # 这个季度
    thisyear = "thisyear"  # 今年
    lastYear = "lastYear"  # 去年
    n_daysago = "%ddaysago"  # {n}daysago: 最近
    n_dayslater = "%ddayslater"  # {n}dayslater: n天以内
    n_weeksago = "%dweeksago"  # {n}weeksago: 最近n周
    weekslater = "%dweekslater"  # {n}weekslater: n周以内
    monthsago = "%dmonthsago"  # {n}monthsago: 最近n月
    monthslater = "%dmonthslater"  # {n}monthslater: n月以内
    quartersago = "%dquartersago"  # {n}quartersago: 最近n季度
    quarterslater = "%dquarterslater"  # {n}quarterslater: n季度以内
    yearsago = "%dyearsago"  # {n}yearsago: 最近n年
    yearslater = "%dyearslater"  # {n}yearslater: n年以内


class FormMode(Enum):
    none = ""
    normal = "normal"
    horizontal = "horizontal"
    inline = "inline"


class Shape(Enum):
    none = ""
    circle = "circle"
    square = "square"


class OptionType(Enum):
    none = ""
    default = "default"
    button = "button"


class FlexJustify(Enum):
    none = ""
    start = "start"
    flexStart = "flex-start"
    center = "center"
    end = "end"
    flexEnd = "flex-end"
    spaceAround = "space-around"
    spaceBetween = "space-between"
    spaceEvenly = "space-evenly"


class FlexAlignItems(Enum):
    none = ""
    stretch = "stretch"
    start = "start"
    flexStart = "flex-start"
    center = "center"
    end = "end"
    flexEnd = "flex-end"
    baseline = "baseline"


class TabsMode(Enum):
    none = ""
    line = "line"
    card = "card"
    radio = "radio"
    vertical = "vertical"
    chrome = "chrome"
    simple = "simple"
    strong = "strong"
    tiled = "tiled"
    sidebar = "sidebar"


class ColorFormat(Enum):
    none = ""
    hex = "hex"
    rgb = "rgb"
    rgba = "rgba"
    hsl = "hsl"


class TabsStyle(Enum):
    none = ""
    line = "line"
    card = "card"
    radio = "radio"


class BorderMode(Enum):
    none = "none"
    full = "full"
    half = "half"


class Theme(Enum):
    light = "light"
    dark = "dark"


# ----------------  定义基础类型   ---------------------

class Renderable:
    def __init__(self):
        self.enablePrint: bool = False

    def render(self) -> MixedDict:
        res = {}
        for key, value in self.__dict__.items():
            if key.startswith("_") or key == "enablePrint":
                continue
            if value is None:
                continue
            # if value == "":
            #     continue
            if key == "debug" and not value:
                continue
            if isinstance(value, Dict) and value == {}:
                continue
            if isinstance(value, List) and len(value) == 0:
                continue
            if isinstance(value, Enum) and value.value == "":
                continue
            if isinstance(value, Function):
                res[key] = "/Function(" + value.content + ")/"
                continue

            res[key] = self._recur_render(value)

        if self.enablePrint:
            print(json.dumps(res, indent=2, ensure_ascii=False))
        return res

    def dumps(self):
        return json.dumps(self.render(), indent=2, ensure_ascii=False)

    def _recur_render(self, value):
        if value is None:
            return value
        if isinstance(value, (str, bool, int, float, Dict)):
            return value
        elif isinstance(value, List):
            res = []
            for item in value:
                res.append(self._recur_render(item))
            return res
        elif isinstance(value, Enum):
            return value.value
        else:
            return value.render()
            # try:
            #     return value.render()
            # except Exception as ex:
            #     print(value, isinstance(value, NoneSchema))
            #     print(ex)


class BaseSchemaWithoutType(Renderable):
    """
    BaseSchemaWithoutType

    """

    def __init__(self):
        super().__init__()
        # 容器 css 类名
        self.className: SchemaClassName = None
        #  配合 definitions 一起使用，可以实现无限循环的渲染器。
        self.ref: str = None
        # 是否禁用
        self.disabled: bool = None
        # 是否禁用表达式
        self.disabledOn: SchemaExpression = None
        # 是否隐藏
        self.visible: bool = None
        # 是否显示表达式
        self.visibleOn: SchemaExpression = None
        # 组件唯一 id，主要用于日志采集
        self.id: str = None
        # 事件动作配置
        self.onEvent: OnEvent = None
        # 是否静态展示
        self.static: bool = None
        # 是否静态展示表达式
        self.staticOn: SchemaExpression = None
        # 静态展示空值占位
        self.staticPlaceholder: SchemaExpression = None
        # 静态展示表单项类名
        self.staticClassName: SchemaClassName = None
        # 静态展示表单项Label类名
        self.staticLabelClassName: SchemaClassName = None
        # 静态展示表单项Value类名
        self.staticInputClassName: SchemaClassName = None
        self.staticSchema: object = None
        # 组件样式
        self.style: Dict[str, object] = {}


class BaseSchema(BaseSchemaWithoutType):
    def __init__(self):
        super().__init__()
        self.type: SchemaType = ""


class Remark(BaseSchema):
    def __init__(self,
                 content: str = "",
                 **kwargs
                 ):
        super().__init__()
        self.type = "remark"
        self.className = ""
        self.content = content
        self.placement: Placement = Placement.none
        self.trigger: Trigger = Trigger.none
        self.icon: str = ""
        self.shape: Shape = Shape.none
        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


class FormItemSchema(BaseSchema):
    """
    普通表单项属性表
    """

    def __init__(self):
        super().__init__()
        self.type: str = ""
        self.name: str = ""
        self.value: str = ""
        self.label: str = ""
        self.className: str = ""
        self.inputClassName: str = ""
        self.labelClassName: str = ""
        self.labelAlign: LabelAlign = LabelAlign.right
        self.labelRemark: MixedDict = ""
        self.description: str = ""
        self.placeholder: str = ""
        self.inline: bool = False
        self.disabled: bool = False
        self.disabledOn: any = None
        self.visible: bool = True
        self.visibleOn: any = False
        self.required: bool = False
        self.validations: Dict = {}
        self.validationErrors: Dict = {}


class NoneSchema(Renderable):
    def __init__(self):
        super().__init__()

    def render(self) -> MixedDict:
        return None

    def __str__(self):
        return ""


class StrSchema(Renderable):
    def __init__(self, value: str):
        super().__init__()
        self.value = value

    def render(self) -> MixedDict:
        return self.value

    def __str__(self):
        return self.value


class DictSchema(Renderable):
    def __init__(self, **kwargs):
        super().__init__()
        for key, value in kwargs.items():
            self.__dict__[key] = value


class TooltipSchema(Renderable):
    def __init__(self,
                 title: str = "",
                 content: str = "",
                 **kwargs
                 ):
        super().__init__()
        # 标题
        self.title: str = title
        # 内容
        self.content: str = content
        for key, value in kwargs.items():
            self.__dict__[key] = value


SchemaNode = Union[str, BaseSchema, Dict[str, object], List[BaseSchema]]
EmptySchema = NoneSchema()

SchemaTooltip = Literal[str, TooltipSchema]


def wrap(value) -> SchemaNode:
    if value is None:
        return NoneSchema()
    if isinstance(value, (str, int, bool, float)):
        return StrSchema(value)
    if isinstance(value, Dict):
        return DictSchema(**value)
    return value


# class Option(BaseSchema, Generic[T]):
#     def __init__(
#             self, label: str, value: str = None, children: List[T] = None, **kwargs
#     ):
#         super().__init__()
#         self.label = label
#         self.value = value
#         self.children = children
#         for _key, _value in kwargs.items():
#             self.__dict__[_key] = _value


# ----------------  定义事件   ---------------------
class BaseEvent(Dict):
    def __init__(self):
        super().__init__()
        self["type"]: str = ""
        self["actions"]: List[Dict] = []

    def add_action(self, action: Dict):
        self["actions"].append(action)
        return self


class OnEvent(Dict):
    def __init__(self,
                 events: List[BaseEvent] = None):
        super().__init__()
        if events is None:
            return

        for event in events:
            self[event["type"]] = event

    def add_event(self, event: BaseEvent):
        self[event["type"]] = event
        return self


class InitEvent(BaseEvent):
    """
    组件实例被创建并插入 DOM 中时触发。
    """

    def __init__(self,
                 actions: List[Dict] = None
                 ):
        super().__init__()
        self["type"] = "init"
        self["actions"] = []
        if actions is None:
            return
        if len(actions) > 0:
            self["actions"] = actions


class InitedEvent(BaseEvent):
    """
    initApi 接口请求完成时触发

    responseData: any 请求的响应数据
    responseStatus: number 响应状态，0 表示成功
    responseMsg: string响应消息, error表示接口是否成功
    [name]: any 当前数据域中指定字段的值
    """

    def __init__(self,
                 actions: List[Dict] = None
                 ):
        super().__init__()
        self["type"] = "inited"
        self["actions"] = []
        if actions is None:
            return
        if len(actions) > 0:
            self["actions"] = actions


class ChangeEvent(BaseEvent):
    def __init__(self,
                 actions: List[Dict] = None):
        super().__init__()
        self["type"] = "change"
        self["actions"] = []
        if actions is None:
            return
        if len(actions) > 0:
            self["actions"] = actions


class ClickEvent(BaseEvent):
    def __init__(self,
                 actions: List[Dict] = None):
        super().__init__()
        self["type"] = "click"
        self["actions"] = []
        if actions is None:
            return
        if len(actions) > 0:
            self["actions"] = actions


class MouseEnterEvent(BaseEvent):
    def __init__(self,
                 actions: List[Dict] = None):
        super().__init__()
        self["type"] = "mouseenter"
        self["actions"] = []
        if actions is None:
            return
        if len(actions) > 0:
            self["actions"] = actions


class MouseLeaveEvent(BaseEvent):
    def __init__(self,
                 actions: List[Dict] = None):
        super().__init__()
        self["type"] = "mouseleave"
        self["actions"] = []
        if actions is None:
            return
        if len(actions) > 0:
            self["actions"] = actions


class Function:
    def __init__(self,
                 content: str = "",
                 **kwargs
                 ):
        self.content: str = content
        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


SchemaFunction = Union[str, Function]


# ----------------  定义组件   ---------------------
class QsOptions(Renderable):
    def __init__(self,
                 arrayFormat: Literal["", 'indices', 'brackets', 'repeat', 'comma'] = "",
                 indices: bool = None,
                 allowDots: bool = None,
                 ):
        super().__init__()
        self.arrayFormat: Literal["", 'indices', 'brackets', 'repeat', 'comma'] = arrayFormat
        self.indices: bool = indices
        self.allowDots: bool = allowDots


class Api(BaseSchema):
    def __init__(self,
                 url: SchemaUrlPath = "",
                 method: Method = Method.get,
                 data: Dict[str, object] = None,
                 headers: Dict[str, object] = None,
                 convertKeyToPath: bool = None,
                 responseData: Dict[str, object] = None,
                 dataType: Literal["", "json", "form-data", "form"] = "",
                 responseType: str = None,
                 sendOn: SchemaExpression = "",
                 replaceData: bool = None,
                 autoRefresh: bool = None,
                 trackExpression: str = None,
                 cache: Number = None,
                 forceAppendDataToQuery: bool = None,
                 qsOptions: QsOptions = None,
                 silent: bool = None,
                 downloadFileName: str = None,
                 **kwargs,
                 ):
        super().__init__()
        # API 发送目标地址
        self.url: SchemaUrlPath = url
        # API 发送类型
        self.method: Method = method
        # 用来控制携带数据. 当key 为 `&` 值为 `$$` 时, 将所有原始数据打平设置到 data 中. 当值为 $$ 将所有原始数据赋值到对应的 key 中. 当值为 $ 打头时, 将变量值设置到 key 中.
        self.data: Dict[str, object] = data
        # 携带 headers，用法和 data 一样，可以用变量。
        self.headers: Dict[str, object] = headers
        # 默认数据映射中的key如果带点，或者带大括号，会转成对象比如：
        # {
        #     'a.b': '123'
        # }
        #
        # 经过数据映射后变成
        # {
        #     a: {
        #       b: '123
        #      }
        # }
        # 如果想要关闭此功能，请设置 convertKeyToPath 为 false
        self.convertKeyToPath: bool = convertKeyToPath
        # 用来做接口返回的数据映射。
        self.responseData: Dict[str, object] = responseData
        # 发送体的格式
        self.dataType: Literal["", "json", "form-data", "form"] = dataType
        # 如果是文件下载接口，请配置'blob'
        self.responseType: str = responseType
        # 设置发送条件
        self.sendOn: SchemaExpression = sendOn
        # 默认都是追加模式，如果想完全替换把这个配置成 true
        self.replaceData: bool = replaceData
        # 是否自动刷新，当 url 中的取值结果变化时，自动刷新数据。
        self.autoRefresh: bool = autoRefresh
        # 当开启自动刷新的时候，默认是 api 的 url 来自动跟踪变量变化的。 如果你希望监控 url 外的变量，请配置 trackExpression。
        self.trackExpression: str = trackExpression
        # 如果设置了值，同一个接口，相同参数，指定的时间（单位：ms）内请求将直接走缓存。
        self.cache: Number = cache
        # * 强制将数据附加在 query，默认只有 api 地址中没有用变量的时候 crud 查询接口才会
        # * 自动附加数据到 query 部分，如果想强制附加请设置这个属性。
        # * 对于那种临时加了个变量但是又不想全部参数写一遍的时候配置很有用。
        self.forceAppendDataToQuery: bool = forceAppendDataToQuery
        # qs 配置项
        self.qsOptions: QsOptions = qsOptions
        # autoFill 是否显示自动填充错误提示
        self.silent: bool = silent
        # 文件下载时，指定文件名
        self.downloadFileName: str = downloadFileName

        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


SchemaApi = Union[Api, str]


class Action(BaseSchema):
    def __init__(self,
                 id: str = None,
                 type: str = None,
                 disabledTip: str = None,
                 block: bool = None,
                 icon: SchemaIcon = None,
                 iconClassName: SchemaClassName = None,
                 rightIcon: SchemaIcon = None,
                 rightIconClassName: SchemaClassName = None,
                 loadingClassName: SchemaClassName = None,
                 label: str = None,
                 tooltip: SchemaTooltip = None,
                 tooltipPlacement: Placement = None,
                 actionType: ActionType = None,
                 level: Level = None,
                 size: Size = None,
                 confirmText: str = None,
                 required: List[str] = None,
                 activeClassName: str = None,
                 close: Union[str, bool] = None,
                 requireSelected: bool = None,
                 body: SchemaNode = None,
                 loadingOn: str = None,
                 hotKey: str = None,
                 badge: str = None,
                 countDownTpl: str = None,
                 countDown: Number = None,
                 target: str = None,
                 mergeData: bool = None,
                 onClick: Function = None,
                 **kwargs
                 ):
        super().__init__()
        # 主要用于用户行为跟踪里区分是哪个按钮
        self.id: str = id
        # 指定按钮类型，支持 button、submit或者reset三种类型。
        self.type: str = type
        # 按钮文字
        self.label: str = label
        # 子内容
        self.body: SchemaNode = body
        self.actionType: ActionType = actionType
        # 按钮样式
        self.level: Level = level
        # 按钮大小
        self.size: Size = size
        # 按钮图标， iconfont 的类名
        self.icon: SchemaIcon = icon
        # icon 上的css 类名
        self.iconClassName: SchemaClassName = iconClassName
        # 右侧按钮图标， iconfont 的类名
        self.rightIcon: SchemaIcon = rightIcon
        # 右侧 icon 上的 css 类名
        self.rightIconClassName: SchemaClassName = rightIconClassName
        # loading 上的css 类名
        self.loadingClassName: SchemaClassName = loadingClassName
        # 是否显示loading效果
        self.loadingOn: str = loadingOn
        # 键盘快捷键
        self.hotKey: str = hotKey
        # 激活状态时的类名
        self.activeClassName: str = activeClassName
        # 是否为块状展示，默认为内联。
        self.block: bool = block
        self.tooltip: SchemaTooltip = tooltip
        # 禁用时的文案提示。
        self.disabledTip: str = disabledTip
        self.tooltipPlacement: Placement = tooltipPlacement
        # 如果按钮在弹框中，可以配置这个动作完成后是否关闭弹窗，或者指定关闭目标弹框。
        self.close: Union[str, bool] = close
        # 提示文字，配置了操作前会要求用户确认。
        self.confirmText: str = confirmText
        # 如果按钮在form中，配置此属性会要求用户把指定的字段通过验证后才会触发行为。
        self.required: List[str] = required
        # 当按钮时批量操作按钮时，默认必须有勾选元素才能可点击，如果此属性配置成 false，则没有点选成员也能点击。
        self.requireSelected: bool = requireSelected
        # 角标
        self.badge: str = badge
        # 倒计时文字自定义
        self.countDownTpl: str = countDownTpl
        # 点击后的禁止倒计时（秒）
        self.countDown: Number = countDown
        # 可以指定让谁来触发这个动作。
        self.target: str = target
        # 是否将弹框中数据 merge 到父级作用域。
        self.mergeData: bool = mergeData
        # 自定义事件处理函数
        self.onClick: Function = onClick
        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


class ToastItem(BaseSchema):
    def __init__(self,
                 title: SchemaNode = None,
                 body: SchemaNode = None,
                 level: Level = Level.none,
                 position: Position = Position.none,
                 closeButton: bool = False,
                 showIcon: bool = False,
                 timeout: Number = None,
                 allowHtml: bool = True,
                 **kwargs
                 ):
        super().__init__()
        self.title: SchemaNode = title
        self.body: SchemaNode = body
        self.level: Level = level
        self.position: Position = position
        self.closeButton: bool = closeButton
        self.showIcon: bool = showIcon
        self.timeout: Number = timeout
        self.allowHtml: bool = allowHtml
        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


class Toast(BaseSchema):
    def __init__(self,
                 title: str = "",
                 position: Position = Position.none,
                 closeButton: bool = False,
                 showIcon: bool = False,
                 timeout: Number = None,
                 items: List[ToastItem] = None,
                 msgType: str = "",
                 msg: str = "",
                 className: str = "",
                 **kwargs
                 ):
        super().__init__()
        self.title: str = title
        self.position: Position = position
        self.closeButton: bool = closeButton
        self.showIcon: bool = showIcon
        self.timeout: Number = timeout
        self.items: List[ToastItem] = items
        self.msgType: str = msgType
        self.msg: str = msg
        self.className: str = className
        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


class SchemaMessage(Renderable):
    """
    Args:
        success: ajax 操作成功后提示，可以不指定，不指定时以 api 返回为准。
        failed: ajax 操作失败提示。
        fetchFailed: string		获取失败时提示
        saveOrderFailed: string		保存顺序失败提示
        saveOrderSuccess: string		保存顺序成功提示
        quickSaveFailed: string		快速保存失败提示
        quickSaveSuccess: string		快速保存成功提示
    """

    def __init__(self,
                 success: str = "",
                 failed: str = "",
                 fetchSuccess: str = "",
                 fetchFailed: str = "",
                 saveFailed: str = "",
                 saveSuccess: str = "",
                 saveOrderFailed: str = "",
                 saveOrderSuccess: str = "",
                 quickSaveFailed: str = "",
                 quickSaveSuccess: str = "",
                 **kwargs
                 ):
        super().__init__()
        self.success: str = success
        self.failed: str = failed
        # 获取成功的提示，默认为空。
        self.fetchSuccess: str = fetchSuccess
        # 获取失败时的提示
        self.fetchFailed: str = fetchFailed
        # 保存失败时的提示。
        self.saveFailed: str = saveFailed
        # 保存成功时的提示。
        self.saveSuccess: str = saveSuccess
        self.saveOrderFailed: str = saveOrderFailed
        self.saveOrderSuccess: str = saveOrderSuccess
        self.quickSaveFailed: str = quickSaveFailed
        self.quickSaveSuccess: str = quickSaveSuccess
        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


class FormHorizontal(Renderable):
    def __init__(self,
                 left: Number = None,
                 right: Number = None,
                 leftFixed: Union[bool, Number, Literal["xs", "sm", "md", "lg"]] = None,
                 justify: bool = None,
                 labelAlign: LabelAlign = None,
                 labelWidth: NumberAndStr = None,
                 ):
        super().__init__()
        self.left: Number = left
        self.right: Number = right
        self.leftFixed: Union[bool, Number, Literal["xs", "sm", "md", "lg"]] = leftFixed
        # 两端对齐
        self.justify: bool = justify
        # label对齐方式
        self.labelAlign: LabelAlign = labelAlign
        # label自定义宽度，默认单位为px
        self.labelWidth: NumberAndStr = labelWidth


class FormBaseControl(BaseSchema):
    def __init__(self,
                 size: Size = None,
                 label: str = None,
                 labelAlign: LabelAlign = None,
                 labelWidth: NumberAndStr = None,
                 labelClassName: str = None,
                 name: str = None,
                 extraName: str = None,
                 remark: Remark = None,
                 labelRemark: Remark = None,
                 hint: str = None,
                 submitOnChange: bool = None,
                 readOnly: bool = None,
                 readOnlyOn: str = None,
                 validateOnChange: bool = None,
                 description: str = None,
                 descriptionClassName: SchemaClassName = None,
                 mode: Literal["normal", "inline", "horizontal"] = None,
                 horizontal: FormHorizontal = None,
                 inline: bool = None,
                 inputClassName: SchemaClassName = None,
                 placeholder: str = None,
                 required: bool = None,
                 validationErrors: Dict[str, object] = None,
                 validations: Union[str, Dict[str, object]] = None,
                 value: object = None,
                 clearValueOnHidden: bool = None,
                 validateApi: SchemaApi = None,
                 **kwargs,
                 ):
        super().__init__()
        # 表单项大小
        self.size: Size = size
        # 描述标题
        self.label: str = label
        # 描述标题
        self.labelAlign: LabelAlign = labelAlign
        # label自定义宽度，默认单位为px
        self.labelWidth: NumberAndStr = labelWidth
        # 配置 label className
        self.labelClassName: str = labelClassName
        # 字段名，表单提交时的 key，支持多层级，用.连接，如： a.b.c
        self.name: str = name
        # 额外的字段名，当为范围组件时可以用来将另外一个值打平出来
        self.extraName: str = extraName
        # 显示一个小图标, 鼠标放上去的时候显示提示内容
        self.remark: Remark = remark
        # 显示一个小图标, 鼠标放上去的时候显示提示内容, 这个小图标跟 label 在一起
        self.labelRemark: Remark = labelRemark
        # 输入提示，聚焦的时候显示
        self.hint: str = hint
        # 当修改完的时候是否提交表单。
        self.submitOnChange: bool = submitOnChange
        # 是否只读
        self.readOnly: bool = readOnly
        # 只读条件
        self.readOnlyOn: str = readOnlyOn
        # 不设置时，当表单提交过后表单项每次修改都会触发重新验证，
        # 如果设置了，则由此配置项来决定要不要每次修改都触发验证。
        self.validateOnChange: bool = validateOnChange
        # 描述内容，支持 Html 片段。
        self.description: str = description
        # 配置描述上的 className
        self.descriptionClassName: SchemaClassName = descriptionClassName
        # 配置当前表单项展示模式
        self.mode: Literal["normal", "inline", "horizontal"] = mode
        # 当配置为水平布局的时候，用来配置具体的左右分配。
        self.horizontal: FormHorizontal = horizontal
        # 表单 control 是否为 inline 模式。
        self.inline: bool = inline
        # 配置 input className
        self.inputClassName: SchemaClassName = inputClassName
        # 占位符
        self.placeholder: str = placeholder
        # 是否为必填
        self.required: bool = required
        # 验证失败的提示信息
        self.validationErrors: Dict[str, object] = validationErrors
        # 验证规则
        self.validations: Union[str, Dict[str, object]] = validations
        # 默认值，切记只能是静态值，不支持取变量，跟数据关联是通过设置 name 属性来实现的。
        self.value: object = value
        # 表单项隐藏时，是否在当前 Form 中删除掉该表单项值。注意同名的未隐藏的表单项值也会删掉
        self.clearValueOnHidden: bool = clearValueOnHidden
        # 远端校验表单项接口
        self.validateApi: SchemaApi = validateApi

        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


class FormBaseControlSchema(FormBaseControl):
    def __init__(self,
                 type: SchemaType = None,
                 remark: Remark = None,
                 labelRemark: Remark = None,
                 ):
        super().__init__()
        self.type: SchemaType = type
        self.remark: Remark = remark
        self.labelRemark: Remark = labelRemark


class Option(Renderable):
    def __init__(self,
                 label: str = None,
                 scopeLabel: str = None,
                 value: object = None,
                 disabled: bool = None,
                 children: List[T] = None,
                 visible: bool = None,
                 description: str = None,
                 defer: bool = None,
                 deferApi: SchemaApi = None,
                 loading: bool = None,
                 loaded: bool = None,
                 **kwargs,
                 ):
        super().__init__()
        # 用来显示的文字
        self.label: str = label
        # 可以用来给 Option 标记个范围，让数据展示更清晰。
        self.scopeLabel: str = scopeLabel
        # 请保证数值唯一，多个选项值一致会认为是同一个选项。
        self.value: object = value
        # 是否禁用
        self.disabled: bool = disabled
        # 支持嵌套
        self.children: List[Option] = children
        # 是否可见
        self.visible: bool = visible
        # 描述，部分控件支持
        self.description: str = description
        # 标记后数据延时加载
        self.defer: bool = defer
        # 如果设置了，优先级更高，不设置走 source 接口加载。
        self.deferApi: SchemaApi = deferApi
        # 标记正在加载。只有 defer 为 true 时有意义。内部字段不可以外部设置
        self.loading: bool = loading
        # 只有设置了 defer 才有意义，内部字段不可以外部设置
        self.loaded: bool = loaded

        for _key, _value in kwargs.items():
            self.__dict__[_key] = _value


MixedOption = Union[List[Option], List[str], None]


class FormOptionsControl(FormBaseControl):
    def __init__(self,
                 options: Union[List[Option], List[str], PlainObject] = None,
                 source: SchemaApi = None,
                 selectFirst: bool = None,
                 multiple: bool = None,
                 joinValues: bool = None,
                 delimiter: str = None,
                 valuesNoWrap: bool = None,
                 extractValue: bool = None,
                 clearable: bool = None,
                 resetValue: str = None,
                 deferField: str = None,
                 ):
        super().__init__()
        # 选项集合
        self.options: Union[List[Option], List[str], PlainObject] = options
        # 可用来通过 API 拉取 options。
        self.source: SchemaApi = source
        # 默认选择选项第一个值。
        self.selectFirst: bool = selectFirst
        # 是否为多选模式
        self.multiple: bool = multiple
        # 单选模式：当用户选中某个选项时，选项中的 value 将被作为该表单项的值提交，否则，整个选项对象都会作为该表单项的值提交。
        # 多选模式：选中的多个选项的 `value` 会通过 `delimiter` 连接起来，否则直接将以数组的形式提交值。
        self.joinValues: bool = joinValues
        # 分割符
        self.delimiter: str = delimiter
        # 多选模式，值太多时是否避免折行
        self.valuesNoWrap: bool = valuesNoWrap
        # 开启后将选中的选项 value 的值封装为数组，作为当前表单项的值。
        self.extractValue: bool = extractValue
        # 是否可清除。
        self.clearable: bool = clearable
        # 点清除按钮时，将表单项设置成当前配置的值。
        self.resetValue: str = resetValue
        # 懒加载字段
        self.deferField: str = deferField


class PopOver(Renderable):
    def __init__(self,
                 ):
        super().__init__()


SchemaPopOver = Union[str, PopOver]


class ComboCondition(Renderable):
    def __init__(self,
                 test: str = None,
                 items: List[BaseSchema] = None,
                 label: str = None,
                 scaffold: object = None,
                 mode: str = None,
                 ):
        super().__init__()
        self.test: str = test
        self.items: List[BaseSchema] = items
        self.label: str = label
        self.scaffold: object = scaffold
        self.mode: str = mode


class Badge(BaseSchema):
    def __init__(self,
                 className: str = None,
                 text: NumberAndStr = None,
                 size: Number = None,
                 mode: Literal["text", "dot", "ribbon"] = None,
                 offset: NumberAndStr = None,
                 **kwargs,
                 ):
        super().__init__()
        self.className: str = className
        # 文本内容
        self.text: NumberAndStr = text
        # 大小
        self.size: Number = size
        # 角标类型
        self.mode: Literal["text", "dot", "ribbon"] = mode
        # 角标位置，相对于position的位置进行偏移
        self.offset: NumberAndStr = offset

        for key, value in kwargs.items():
            self.__dict__[key] = value


class Icon(BaseSchema):
    def __init__(self,
                 icon: SchemaTpl = None,
                 vendor: Literal["iconfont", "fa", ""] = None,
                 badge: Badge = None,
                 **kwargs,
                 ):
        super().__init__()
        self.type: str = "icon"
        self.icon: SchemaTpl = icon
        self.vendor: Literal["iconfont", "fa", ""] = vendor
        self.badge: Badge = badge
        for key, value in kwargs.items():
            self.__dict__[key] = value


class VariableItem(Renderable):
    def __init__(self,
                 label: str = None,
                 value: str = None,
                 path: str = None,
                 children: List[T] = None,
                 type: str = None,
                 tag: str = None,
                 selectMode: Literal["tree", "tabs"] = None,
                 isMember: bool = None,
                 **kwargs,
                 ):
        super().__init__()
        self.label: str = label
        self.value: str = value
        self.path: str = path
        self.children: List[VariableItem] = children
        self.type: str = type
        self.tag: str = tag
        self.selectMode: Literal["tree", "tabs"] = selectMode
        self.isMember: bool = isMember
        for key, value in kwargs.items():
            self.__dict__[key] = value


class FuncItem(Renderable):
    def __init__(self,
                 name: str = None,
                 example: str = None,
                 description: str = None,
                 **kwargs,
                 ):
        super().__init__()
        # 函数名
        self.name: str = name
        # 示例
        self.example: str = example
        # 描述
        self.description: str = description
        for key, value in kwargs.items():
            self.__dict__[key] = value


class FuncGroup(Renderable):
    def __init__(self,
                 groupName: str = None,
                 items: List[FuncItem] = None,
                 **kwargs,
                 ):
        super().__init__()
        self.groupName: str = groupName
        self.items: List[FuncItem] = items
        for key, value in kwargs.items():
            self.__dict__[key] = value


class ValidationConfig(Renderable):
    def __init__(self,
                 errorMode: Literal["full", "partial"] = None,
                 delimiter: str = None,
                 **kwargs,
                 ):
        super().__init__()
        # 错误提示的展示模式, full为整体飘红, highlight为仅错误项飘红, 默认为full
        self.errorMode: Literal["full", "partial"] = errorMode
        # 单个子元素多条校验信息的分隔符
        self.delimiter: str = delimiter
        for key, value in kwargs.items():
            self.__dict__[key] = value


class Limit(Renderable):
    def __init__(self,
                 aspectRatioLabel: str = None,
                 aspectRatio: Number = None,
                 height: Number = None,
                 width: Number = None,
                 maxHeight: Number = None,
                 maxWidth: Number = None,
                 minHeight: Number = None,
                 minWidth: Number = None,
                 **kwargs,
                 ):
        super().__init__()
        # 比率不对时的提示文字
        self.aspectRatioLabel: str = aspectRatioLabel
        # 限制比率
        self.aspectRatio: Number = aspectRatio
        # 限制图片高度
        self.height: Number = height
        # 限制图片宽度
        self.width: Number = width
        # 限制图片最大高度
        self.maxHeight: Number = maxHeight
        # 限制图片最大宽度
        self.maxWidth: Number = maxWidth
        # 限制图片最小高度
        self.minHeight: Number = minHeight
        # 限制图片最小宽度
        self.minWidth: Number = minWidth

        for key, value in kwargs.items():
            self.__dict__[key] = value


SchemaCollection = Union[BaseSchema, SchemaNode, SchemaTpl, List[SchemaNode]]


class TooltipWrapper(BaseSchema):
    def __init__(self,
                 title: str = None,
                 content: str = None,
                 placement: Placement = None,
                 offset: any = None,
                 showArrow: bool = None,
                 disabled: bool = None,
                 trigger: Union[Trigger, List[Trigger]] = None,
                 mouseEnterDelay: Number = None,
                 mouseLeaveDelay: Number = None,
                 rootClose: bool = None,
                 body: SchemaCollection = None,
                 wrapperComponent: str = None,
                 inline: bool = None,
                 tooltipTheme: Theme = None,
                 style: Dict[str, object] = None,
                 enterable: bool = None,
                 tooltipStyle: Dict[str, object] = None,
                 className: str = None,
                 tooltipClassName: str = None,
                 **kwargs,
                 ):
        super().__init__()
        self.type = "tooltip-wrapper"
        # 文字提示标题
        self.title: str = title
        # 文字提示内容，兼容 tooltip，但建议通过 content 来实现提示内容
        self.content: str = content
        # 文字提示浮层出现位置，默认为top
        self.placement: Placement = placement
        # 浮层位置相对偏移量, 默认值：[0, 0]
        self.offset: any = offset
        # 是否展示浮层指向箭头
        self.showArrow: bool = showArrow
        # 是否禁用提示
        self.disabled: bool = disabled
        # 浮层触发方式，默认为hover
        self.trigger: Union[Trigger, List[Trigger]] = trigger
        # 浮层延迟显示时间, 单位 ms
        self.mouseEnterDelay: Number = mouseEnterDelay
        # 浮层延迟隐藏时间, 单位 ms
        self.mouseLeaveDelay: Number = mouseLeaveDelay
        # 是否点击非内容区域关闭提示，默认为true
        self.rootClose: bool = rootClose
        # 内容区域
        self.body: SchemaCollection = body
        # 内容区包裹标签
        self.wrapperComponent: str = wrapperComponent
        # 内容区是否内联显示，默认为false
        self.inline: bool = inline
        # 主题样式， 默认为light
        self.tooltipTheme: Theme = tooltipTheme
        # 内容区自定义样式
        self.style: Dict[str, object] = style
        # 是否可以移入浮层中, 默认true
        self.enterable: bool = enterable
        # 自定义提示浮层样式
        self.tooltipStyle: Dict[str, object] = tooltipStyle
        # 内容区CSS类名
        self.className: str = className
        # 文字提示浮层CSS类名
        self.tooltipClassName: str = tooltipClassName

        for key, value in kwargs.items():
            self.__dict__[key] = value


class OverflowConfig(Renderable):
    def __init__(self,
                 maxTagCount: Number = None,
                 displayPosition: List = None,
                 overflowTagPopover: TooltipWrapper = None,
                 overflowTagPopoverInCRUD: TooltipWrapper = None,
                 **kwargs,
                 ):
        super().__init__()
        # 标签的最大展示数量，超出数量后以收纳浮层的方式展示，仅在多选模式开启后生效
        self.maxTagCount: Number = maxTagCount
        # 开启最大标签展示数量后，收纳标签生效的位置，未开启内嵌模式默认为选择器, 开启后默认为选择器 + 模态框，可选值为'select'(选择器)、'crud'(增删改查)
        self.displayPosition: List = displayPosition
        # 开启最大标签展示数量后，选择器内收纳标签的Popover配置
        self.overflowTagPopover: TooltipWrapper = overflowTagPopover
        # 开启最大标签展示数量后，CRUD顶部内收纳标签的Popover配置
        self.overflowTagPopoverInCRUD: TooltipWrapper = overflowTagPopoverInCRUD
        for key, value in kwargs.items():
            self.__dict__[key] = value
